﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos.Weaver;
using PostSharp.CodeModel;
using System.Runtime.Serialization;
using PostSharp.Laos;
using PostSharp.Collections;
using System.Reflection;
using PostSharp.CodeModel.Collections;
using PostSharp.CodeModel.TypeSignatures;

namespace CyclicReferences.Weaver
{
    /// <summary>
    /// Weaver for circular references. Takes into account the originalvalues
    /// and serializes them.
    /// </summary>
    public class Weaver : TypeLevelAspectWeaver
    {
        #region fields
        // TODO: should get these from the module.cache
        SerializationType stringType, boolType, intType;
        ModuleDeclaration module;

        TypeDefDeclaration surrogate;
        TypeDefDeclaration source;

        /// <summary>
        /// interface that is implemented on the target instance.
        /// We ductype it. So it needs to implement a few specific methods
        /// see the validateSelf method
        /// </summary>
        Type originalValueProvider;
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="Weaver"/> class.
        /// </summary>
        /// <param name="OriginalValueProviderName">Name of the original value provider.</param>
        public Weaver(string OriginalValueProviderName)
        {
            // HACK: i pass the type name, because I get into troubles with weakreferences if I pass as type.
            this.originalValueProvider = Type.GetType(OriginalValueProviderName);
        }

        /// <summary>
        /// Implement the current aspect.
        /// </summary>
        /// <remarks>
        /// If implementations need to register an advice for the low-level code
        /// weaver, they can add them in the <see cref="P:PostSharp.Laos.Weaver.LaosTask.MethodLevelAdvices"/>
        /// and <see cref="P:PostSharp.Laos.Weaver.LaosTask.TypeLevelAdvices"/> collections of the
        /// parent <see cref="T:PostSharp.Laos.Weaver.LaosTask"/>.
        /// </remarks>
        public override void Implement()
        {
            #region cache
            module = this.Task.Project.Module;
            // TODO: find out how to use the module.cache for this
            stringType = SerializationType.GetSerializationType(module.FindType(typeof(string), BindingOptions.Default));
            boolType = SerializationType.GetSerializationType(module.FindType(typeof(bool), BindingOptions.Default));
            intType = SerializationType.GetSerializationType(module.FindType(typeof(int), BindingOptions.Default));

            #endregion

            source = (TypeDefDeclaration)this.TargetType;

            // creates a nested surrogate type
            CreateSurrogateType();

            // creates the full ctor with all the copying logic
            WriteFullCtorForSurrogate();

            // write the simple empty ctor, needed for the serialization engine
            WriteEmptyCtorForSurrogate();

            // create the deserialize method inside the original type
            CreateDeserializeMethod();


        }

        /// <summary>
        /// Validates the originalValueProvider and throws exception if it does not implement the correct methods
        /// </summary>
        /// <returns></returns>
        public override bool ValidateSelf()
        {
            // these are methods that are expected on the valueprovider interface
            if (
                originalValueProvider.GetMethod("BeginDeserialization") == null ||
                originalValueProvider.GetMethod("EndDeserialization") == null ||
                originalValueProvider.GetMethod("GetOriginalValue") == null ||
                originalValueProvider.GetMethod("ShouldSerializeOriginalValue") == null ||
                originalValueProvider.GetMethod("SetValueAsOriginal") == null)
            {
                throw new NotSupportedException("The supplied original value provider does implement the methods 'BeginDeserialization', 'EndDeserialization', 'GetOriginalValue', 'ShouldSerializeOriginalValue' or 'SetValueAsOriginal'");
            }


            return base.ValidateSelf();
        }



        #region implementation routines
        /// <summary>
        /// Creates the type of the surrogate.
        /// </summary>
        private void CreateSurrogateType()
        {
            surrogate = new TypeDefDeclaration();
            surrogate.Name = source.Name + "Surrogate";
            surrogate.Attributes = source.Attributes | TypeAttributes.NestedPublic;
            source.Types.Add(surrogate);

            CopyDataContractAttribute(source, surrogate);
        }

        /// <summary>
        /// Writes the full ctor for surrogate.
        /// This sets up the ctor with two params: the source object and a list with already serialized objects
        /// Then it checks if it is already in the list. If not, adds itself and calls to copy the properties and fields
        /// to the surrogate
        /// </summary>
        private void WriteFullCtorForSurrogate()
        {
            #region preparations
            InstructionWriter writer = this.Task.InstructionWriter;

            InstructionSequence sequence;

            // two parameters: our source object and a List<object> of objects already serialized by 
            // the surrogate
            MethodDefDeclaration fullCtor = new MethodDefDeclaration();
            fullCtor.Name = ".ctor";
            fullCtor.Attributes = MethodAttributes.Public | MethodAttributes.HideBySig |
                                  MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
            surrogate.Methods.Add(fullCtor);
            ParameterDeclaration sourceobject = new ParameterDeclaration(0, "source", source);
            fullCtor.Parameters.Add(sourceobject);
            ParameterDeclaration graphList = new ParameterDeclaration(1, "graphList", module.Cache.GetType(typeof(List<object>), BindingOptions.RequireGenericInstance));
            fullCtor.Parameters.Add(graphList);

            sequence = fullCtor.MethodBody.CreateInstructionSequence();
            InstructionSequence continueSequence = fullCtor.MethodBody.CreateInstructionSequence();

            fullCtor.MethodBody.RootInstructionBlock = fullCtor.MethodBody.CreateInstructionBlock();
            fullCtor.MethodBody.RootInstructionBlock.AddInstructionSequence(sequence, NodePosition.After, null);
            fullCtor.MethodBody.RootInstructionBlock.AddInstructionSequence(continueSequence, NodePosition.After, null);

            // we need to store a boolean, as output of a future 'contains' call to the list
            LocalVariableSymbol IsInList = fullCtor.MethodBody.RootInstructionBlock.DefineLocalVariable(module.Cache.GetType(typeof(bool)), "isInList");
            // we need to store the id of the object
            LocalVariableSymbol ListID = fullCtor.MethodBody.RootInstructionBlock.DefineLocalVariable(module.Cache.GetType(typeof(int)), "ListID");
            // also need to store a boolean, to determine if we should get the original value
            LocalVariableSymbol ShouldCopyOriginal = fullCtor.MethodBody.RootInstructionBlock.DefineLocalVariable(module.Cache.GetType(typeof(bool)), "shouldCopyOriginal");

            // start with a call create object
            writer.AttachInstructionSequence(sequence);
            writer.EmitInstruction(OpCodeNumber.Ldarg_0);
            writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(typeof(object).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default));
            writer.EmitInstruction(OpCodeNumber.Nop);
            writer.EmitInstruction(OpCodeNumber.Nop);
            #endregion


            #region if object is in the list, jump ahead
            // is the object in the list?
            writer.EmitInstruction(OpCodeNumber.Ldarg_2);
            writer.EmitInstruction(OpCodeNumber.Ldarg_1);
            writer.EmitInstructionMethod(OpCodeNumber.Callvirt, module.FindMethod(typeof(List<object>).GetMethod("Contains"), BindingOptions.RequireGenericInstance));
            writer.EmitInstruction(OpCodeNumber.Stloc_0);

            // go into an if, if it is
            writer.EmitInstruction(OpCodeNumber.Ldloc_0);

            writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, continueSequence);       // see region 'continue point'
            #endregion

            #region it's not, so place it in
            // if we are here, the object was not in the list: place it in!
            writer.EmitInstruction(OpCodeNumber.Ldarg_2);
            writer.EmitInstruction(OpCodeNumber.Ldarg_1);
            writer.EmitInstructionMethod(OpCodeNumber.Callvirt, module.FindMethod(typeof(List<object>).GetMethod("Add"), BindingOptions.RequireGenericInstance));
            writer.EmitInstruction(OpCodeNumber.Nop);
            #endregion


            #region write code to copy property and field definitions and copy values to surrogate
            // now copy the properties and fields that have datamember attributes
            CopyPropertiesToSurrogate(writer, fullCtor);
            CopyFieldsToSurrogate(writer);
            #endregion

            
            writer.DetachInstructionSequence();

            #region continue point: write the serializationID
            // continue after the check if we already serialized ourselves
            writer.AttachInstructionSequence(continueSequence);

            // write the list ID
            writer.EmitInstruction(OpCodeNumber.Ldarg_2);
            writer.EmitInstruction(OpCodeNumber.Ldarg_1);
            writer.EmitInstructionMethod(OpCodeNumber.Callvirt, module.FindMethod(typeof(List<object>).GetMethods().First(mi => mi.Name == "IndexOf"), BindingOptions.RequireGenericInstance));
            writer.EmitInstruction(OpCodeNumber.Stloc_1);

            // create a field to hold it
            FieldDefDeclaration idField = new FieldDefDeclaration();
            surrogate.Fields.Add(idField);
            idField.Name = "SerializationID";
            idField.FieldType = module.Cache.GetType(typeof(int));
            idField.Attributes = FieldAttributes.Public;
            // attribute datamember on it
            IMethod newDataMemberAttributeCtor = module.FindMethod(typeof(DataMemberAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);
            CustomAttributeDeclaration newDataMemberAttribute = new CustomAttributeDeclaration(newDataMemberAttributeCtor);
            idField.CustomAttributes.Add(newDataMemberAttribute);

            // set the serialization id
            writer.EmitInstruction(OpCodeNumber.Ldarg_0);
            writer.EmitInstruction(OpCodeNumber.Ldloc_1);
            writer.EmitInstructionField(OpCodeNumber.Stfld, idField);
            #endregion

            writer.EmitInstruction(OpCodeNumber.Ret);
            writer.DetachInstructionSequence();
        }

        /// <summary>
        /// Writes the empty ctor for surrogate.
        /// </summary>
        private void WriteEmptyCtorForSurrogate()
        {
            // simple ctor that just calls object..ctor
            InstructionWriter writer = this.Task.InstructionWriter;

            InstructionSequence sequence;

            MethodDefDeclaration emptyCtor = new MethodDefDeclaration();
            emptyCtor.Name = ".ctor";
            emptyCtor.Attributes = MethodAttributes.Public | MethodAttributes.HideBySig |
                                   MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
            surrogate.Methods.Add(emptyCtor);

            sequence = emptyCtor.MethodBody.CreateInstructionSequence();
            emptyCtor.MethodBody.RootInstructionBlock = emptyCtor.MethodBody.CreateInstructionBlock();
            emptyCtor.MethodBody.RootInstructionBlock.AddInstructionSequence(sequence, NodePosition.After, null);

            writer.AttachInstructionSequence(sequence);
            writer.EmitInstruction(OpCodeNumber.Ldarg_0);
            writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(typeof(object).GetConstructor(System.Type.EmptyTypes), 
                BindingOptions.Default));
            writer.EmitInstruction(OpCodeNumber.Ret);
            writer.DetachInstructionSequence();
        }

        /// <summary>
        /// Copies the properties to surrogate.
        /// Called by the WriteFullCtorForSurrogate method.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void CopyPropertiesToSurrogate(InstructionWriter writer, MethodDefDeclaration fullCtor)
        {
            // takes properties that have the datamember attribute applied
            List<PropertyDeclaration> valueTypes = new List<PropertyDeclaration>();

            // get a list of all properties we need to support
            TypeDefDeclaration t = source;
            do
            {

                t.Properties.Where(valtype => valtype.CustomAttributes.FirstOrDefault(attr => attr.ConstructRuntimeObject() is DataMemberAttribute) != null)
                    .Select(valtype => valtype).ToList().ForEach(valtype => valueTypes.Add(valtype));

                t = t.BaseType as TypeDefDeclaration;
            } while (t != null);


            foreach (PropertyDeclaration prop in valueTypes)
            {
                // although it is a property on the real type, the surrogate is fine with a field
                #region Copy the real property
                FieldDefDeclaration p = new FieldDefDeclaration();
                surrogate.Fields.Add(p);
                p.Name = prop.Name;
                p.FieldType = prop.PropertyType;
                p.Attributes = System.Reflection.FieldAttributes.Public;

                // copies the datamember attribute in the sourcecollection
                CopyDataMemberAttribute(prop.CustomAttributes, p.CustomAttributes, false);

                // init the field
                writer.EmitInstruction(OpCodeNumber.Ldarg_0);
                writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                writer.EmitInstructionMethod(OpCodeNumber.Callvirt, prop.Members.GetBySemantic(MethodSemantics.Getter).Method);

                writer.EmitInstructionField(OpCodeNumber.Stfld, p);
                #endregion

                #region copy the original value
                FieldDefDeclaration orig_p = new FieldDefDeclaration();
                surrogate.Fields.Add(orig_p);
                orig_p.Name = "OriginalValue_" + prop.Name;
                orig_p.FieldType = prop.PropertyType;
                orig_p.Attributes = System.Reflection.FieldAttributes.Public;

                // copies the datamember attribute in the sourcecollection
                CopyDataMemberAttribute(prop.CustomAttributes, orig_p.CustomAttributes, true);

                // see if we should copy the original value
                // ** we create a branch here and check if we want to serialize **
                InstructionSequence notCopy = fullCtor.MethodBody.CreateInstructionSequence();
                fullCtor.MethodBody.RootInstructionBlock.AddInstructionSequence(notCopy, NodePosition.After, writer.CurrentInstructionSequence);


                writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                writer.EmitInstructionString(OpCodeNumber.Ldstr, prop.Name);

                writer.EmitInstructionMethod(OpCodeNumber.Callvirt,
                    module.FindMethod(originalValueProvider.GetMethod("ShouldSerializeOriginalValue"), BindingOptions.OnlyDefinition));
                writer.EmitInstruction(OpCodeNumber.Ldc_I4_0);
                writer.EmitInstruction(OpCodeNumber.Ceq);
                writer.EmitInstruction(OpCodeNumber.Stloc_2);   // store it in the shouldserialize bool
                writer.EmitInstruction(OpCodeNumber.Ldloc_2);   // load it on the stack

                writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, notCopy);

                // copy the original value
                writer.EmitInstruction(OpCodeNumber.Ldarg_0);
                writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                writer.EmitInstructionString(OpCodeNumber.Ldstr, prop.Name);

                writer.EmitInstructionMethod(OpCodeNumber.Callvirt,
                    module.FindMethod(originalValueProvider.GetMethod("GetOriginalValue"), BindingOptions.OnlyDefinition ));

                // unbox if valuetype
                if (orig_p.FieldType.BelongsToClassification(TypeClassifications.ValueType))
                    writer.EmitInstructionType(OpCodeNumber.Unbox_Any, orig_p.FieldType);

                writer.EmitInstructionField(OpCodeNumber.Stfld, orig_p);
                writer.EmitInstruction(OpCodeNumber.Nop);
                writer.DetachInstructionSequence();
                writer.AttachInstructionSequence(notCopy);
                #endregion
            }

            writer.EmitInstruction(OpCodeNumber.Nop);
        }

        /// <summary>
        /// Copies the fields to surrogate.
        /// Called by WriteFullCtorForSurrogate method.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void CopyFieldsToSurrogate(InstructionWriter writer)
        {

            foreach (FieldDefDeclaration field in source.Fields)
            {
                if (field.CustomAttributes.FirstOrDefault(attr => attr.ConstructRuntimeObject() is DataMemberAttribute) != null)
                {
                    // has datamember
                    #region copy the real field
                    FieldDefDeclaration f = new FieldDefDeclaration();
                    surrogate.Fields.Add(f);
                    f.Name = field.Name;
                    f.FieldType = field.FieldType;
                    f.Attributes = System.Reflection.FieldAttributes.Public;

                    // copies the datamember attribute in the sourcecollection
                    CopyDataMemberAttribute(field.CustomAttributes, f.CustomAttributes, false);


                    // init the field
                    writer.EmitInstruction(OpCodeNumber.Ldarg_0);
                    writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                    writer.EmitInstructionField(OpCodeNumber.Ldfld, field);
                    writer.EmitInstructionField(OpCodeNumber.Stfld, f);
                    #endregion

                    #region copy the original value
                    //FieldDefDeclaration orig_f = new FieldDefDeclaration();
                    //surrogate.Fields.Add(orig_f);
                    //orig_f.Name = "OriginalValue_" + field.Name;
                    //orig_f.FieldType = field.FieldType;
                    //orig_f.Attributes = System.Reflection.FieldAttributes.Public;

                    //// copies the datamember attribute in the sourcecollection
                    //CopyDataMemberAttribute(field.CustomAttributes, orig_f.CustomAttributes, true);

                    //// init the field
                    //writer.EmitInstruction(OpCodeNumber.Ldarg_0);
                    //writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                    //writer.EmitInstructionString(OpCodeNumber.Ldstr, field.Name);
                    //writer.EmitInstructionType(OpCodeNumber.Ldtoken, field.FieldType);
                    //writer.EmitInstructionMethod(OpCodeNumber.Call,
                    //    module.FindMethod(typeof(Type).GetMethod("GetTypeFromHandle"), BindingOptions.Default));

                    //writer.EmitInstructionMethod(OpCodeNumber.Callvirt,
                    //    module.FindMethod(originalValueProvider.GetMethod("RetrieveLoadedStateFromFieldName"), BindingOptions.Default));

                    //// unbox if valuetype
                    //if (orig_f.FieldType.BelongsToClassification(TypeClassifications.Boxed))
                    //    writer.EmitInstructionType(OpCodeNumber.Unbox_Any, orig_f.FieldType);

                    //writer.EmitInstructionField(OpCodeNumber.Stfld, orig_f); 
                    #endregion
                }

            }

            writer.EmitInstruction(OpCodeNumber.Nop);
        }



        /// <summary>
        /// Creates the 'CopyFromDataSurrogate' method.
        /// </summary>
        private void CreateDeserializeMethod()
        {
            // TODO: let this method implement an interface, so we can call it without reflection

            // we need a method on the original type that will take a surrogate object and copy all it's info
            MethodDefDeclaration deserializeMethod = new MethodDefDeclaration();
            deserializeMethod.Attributes = MethodAttributes.Public;
            
            deserializeMethod.Name = "CopyDataFromSurrogate";
            source.Methods.Add(deserializeMethod);
            ParameterDeclaration surrogateObject = new ParameterDeclaration(0, "surrogate", surrogate);
            deserializeMethod.Parameters.Add(surrogateObject);

            InstructionWriter writer = this.Task.InstructionWriter;
            InstructionSequence seq = deserializeMethod.MethodBody.CreateInstructionSequence();
            deserializeMethod.MethodBody.RootInstructionBlock = deserializeMethod.MethodBody.CreateInstructionBlock();
            deserializeMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(seq, NodePosition.After, null);

            writer.AttachInstructionSequence(seq);


            // notify the start of deserialization
            writer.EmitInstruction(OpCodeNumber.Ldarg_0);
            writer.EmitInstructionMethod(OpCodeNumber.Callvirt,
                module.FindMethod(originalValueProvider.GetMethod("BeginDeserialization"), BindingOptions.Default));

            /* [implement copying from both original values as from normal values] */
            #region implement
            // ldarg.0 is the source
            // ldarg.1 is the surrogate

            // first all the original fields
            #region original values
            foreach (FieldDefDeclaration src in surrogate.Fields)
            {
                // all fields in the surrogate (except SerializationID) are important
                if (src.Name.IndexOf("OriginalValue_") > -1)
                {
                    if (src.FieldType.GetSystemType(null, null).Name == "ICollection`1")
                    {
                        #region collection
                        // check for null
                        InstructionSequence continueseq = deserializeMethod.MethodBody.CreateInstructionSequence();

                        writer.EmitInstruction(OpCodeNumber.Ldarg_1);   // surrogate
                        writer.EmitInstructionField(OpCodeNumber.Ldfld, src);   // put the value on the stack
                        writer.EmitInstruction(OpCodeNumber.Ldnull);    // compare with null

                        writer.EmitInstruction(OpCodeNumber.Ceq);

                        writer.EmitBranchingInstruction(OpCodeNumber.Brtrue, continueseq);

                        // so, it's not null, load it

                        // if we are loading to a ICollection, we want to wrap the array into a list<T>. That is
                        // much better default implementation
                        TypeDefDeclaration genericType = (TypeDefDeclaration) src.FieldType.
                            GetGenericContext(GenericContextOptions.ResolveGenericParameterDefinitions).GetGenericTypeParameters().First();

                        // create a list with that generic type
                        GenericTypeInstanceTypeSignature listOfType = new GenericTypeInstanceTypeSignature((INamedType)
                                                              module.FindType("System.Collections.Generic.List`1, mscorlib",
                                                                               BindingOptions.Default),
                                                                               new ITypeSignature[] { genericType });

                        writer.EmitInstruction(OpCodeNumber.Ldarg_0);   // this
                        writer.EmitInstructionString(OpCodeNumber.Ldstr, src.Name.Replace("OriginalValue_", ""));   // put the value on the stack
                        writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                        writer.EmitInstructionField(OpCodeNumber.Ldfld, src);   // put the value on the stack

                        // TODO: postsharp not yet implemented the getconstructor (line 111, basetypewrapper)
                        writer.EmitInstructionMethod(OpCodeNumber.Newobj,
                            module.FindMethod(listOfType.GetSystemType(new Type[] { genericType.GetReflectionWrapper(null, null) }, null).GetConstructors()[2]
                            , BindingOptions.RequireGenericInstance));
                        writer.EmitInstructionMethod(OpCodeNumber.Call,
                            module.FindMethod(originalValueProvider.GetMethod("SetValueAsOriginal"), BindingOptions.Default));

                        deserializeMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(continueseq, NodePosition.After, writer.CurrentInstructionSequence);
                        writer.DetachInstructionSequence();
                        writer.AttachInstructionSequence(continueseq); 
                        #endregion


                    }
                    else
                    {
                        #region normal field
                        // this is an original value
                        writer.EmitInstruction(OpCodeNumber.Ldarg_0);   // this
                        writer.EmitInstructionString(OpCodeNumber.Ldstr, src.Name.Replace("OriginalValue_", ""));
                        writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                        writer.EmitInstructionField(OpCodeNumber.Ldfld, src);   // put the value on the stack
                        if (src.FieldType.BelongsToClassification(TypeClassifications.ValueType))
                        {
                            writer.EmitInstructionType(OpCodeNumber.Box, src.FieldType);
                        }
                        writer.EmitInstructionMethod(OpCodeNumber.Call,
                            module.FindMethod(originalValueProvider.GetMethod("SetValueAsOriginal"), BindingOptions.Default)); 
                        #endregion
                    }
                }


            } 
            #endregion

            // and now the current values 
            #region current values
            foreach (FieldDefDeclaration src in surrogate.Fields)
            {
                // all fields in the surrogate (except SerializationID) are important

                if (src.Name.IndexOf("OriginalValue_") == -1)
                {
                    CopyFieldFromSurrogate(writer, src, src.Name);
                }
            } 
            #endregion
            #endregion

            // notify the end of deserialization
            writer.EmitInstruction(OpCodeNumber.Ldarg_0);
            writer.EmitInstructionMethod(OpCodeNumber.Callvirt,
                module.FindMethod(originalValueProvider.GetMethod("EndDeserialization"), BindingOptions.Default));


            writer.EmitInstruction(OpCodeNumber.Ret);

            writer.DetachInstructionSequence();


        }

        /// <summary>
        /// IL commands to copy a field from a surrogate.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="src">The source field of the surrogate that we are copying.</param>
        /// <param name="name">The name of the field.</param>
        private void CopyFieldFromSurrogate(InstructionWriter writer, FieldDefDeclaration src, string name)
        {
            #region copy the normal values
            // find the corresponding property on the source

            TypeDefDeclaration t = source;
            PropertyDeclaration targetProp;
            do
            {
                targetProp = t.Properties.FirstOrDefault(prop => prop.Name == name);

                t = t.BaseType as TypeDefDeclaration;
            } while (t != null && targetProp == null);


            if (targetProp != null)
            {
                if (src.FieldType.GetSystemType(null, null).Name == "ICollection`1")
                {
                    // if we are loading to a ICollection, we want to wrap the array into a list<T>. That is
                    // much better default implementation
                    TypeDefDeclaration genericType = (TypeDefDeclaration)src.FieldType.
                        GetGenericContext(GenericContextOptions.ResolveGenericParameterDefinitions).GetGenericTypeParameters().First();

                    // create a list with that generic type
                    GenericTypeInstanceTypeSignature listOfType = new GenericTypeInstanceTypeSignature((INamedType)
                                                          module.FindType("System.Collections.Generic.List`1, mscorlib",
                                                                           BindingOptions.Default),
                                                                           new ITypeSignature[] { genericType });

                    writer.EmitInstruction(OpCodeNumber.Ldarg_0);   // this
                    writer.EmitInstruction(OpCodeNumber.Ldarg_1);   // surrogate
                    writer.EmitInstructionField(OpCodeNumber.Ldfld, src);   // put the value on the stack

                    // TODO: postsharp not yet implemented the getconstructor (line 111, basetypewrapper)
                    writer.EmitInstructionMethod(OpCodeNumber.Newobj,
                        module.FindMethod(listOfType.GetSystemType(new Type[] { genericType.GetReflectionWrapper(null, null) }, null).GetConstructors()[2]
                        , BindingOptions.RequireGenericInstance) );
                    writer.EmitInstructionMethod(OpCodeNumber.Callvirt, targetProp.Members.GetBySemantic(MethodSemantics.Setter).Method);

                }
                else
                {
                    // just load in the field straight away
                    writer.EmitInstruction(OpCodeNumber.Ldarg_0);   // this
                    writer.EmitInstruction(OpCodeNumber.Ldarg_1);   // surrogate
                    writer.EmitInstructionField(OpCodeNumber.Ldfld, src);   // put the value on the stack

                    writer.EmitInstructionMethod(OpCodeNumber.Callvirt, targetProp.Members.GetBySemantic(MethodSemantics.Setter).Method);
                }
            }
            else
            {
                // maybe a field
                // TODO: the fields do not react to linq like they should!
                foreach (FieldDefDeclaration targetField in source.Fields)
                {
                    if (targetField.Name == name)
                    {
                        writer.EmitInstruction(OpCodeNumber.Ldarg_0);
                        writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                        writer.EmitInstructionField(OpCodeNumber.Ldfld, src);   // put the value on the stack

                        writer.EmitInstructionField(OpCodeNumber.Stfld, targetField);
                    }
                }
            }
            #endregion

            writer.EmitInstruction(OpCodeNumber.Nop);
        }

        #endregion


        #region helpers
        private void CopyDataContractAttribute(TypeDefDeclaration source, TypeDefDeclaration target)
        {
            CustomAttributeDeclaration srcDataContractAttr = source.CustomAttributes.FirstOrDefault(attr => attr.ConstructRuntimeObject() is DataContractAttribute);
            DataContractAttribute srcDataContractAttrRuntime = (DataContractAttribute)srcDataContractAttr.ConstructRuntimeObject();

            IMethod mssgDataContractAttributeCtor = module.FindMethod(typeof(DataContractAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);
            CustomAttributeDeclaration msgDataContractAttribute = new CustomAttributeDeclaration(mssgDataContractAttributeCtor);

            msgDataContractAttribute.NamedArguments.Add(
                new MemberValuePair(MemberKind.Property,
                    0,
                    "Name",
                    new SerializedValue(stringType,
                        srcDataContractAttrRuntime.Name)
                        ));

            msgDataContractAttribute.NamedArguments.Add(
                new MemberValuePair(MemberKind.Property,
                    1,
                    "Namespace",
                    new SerializedValue(stringType,
                        srcDataContractAttrRuntime.Namespace)
                        ));

            target.CustomAttributes.Add(msgDataContractAttribute);

            // remove from source
            source.CustomAttributes.Remove(srcDataContractAttr);
        }

        private void CopyDataMemberAttribute(CustomAttributeDeclarationCollection sourceAttributes, CustomAttributeDeclarationCollection target, bool originalValue)
        {
            CustomAttributeDeclaration srcDecl = sourceAttributes.FirstOrDefault(attr => attr.ConstructRuntimeObject() is DataMemberAttribute);
            DataMemberAttribute srcMemberAttr = (DataMemberAttribute)srcDecl.ConstructRuntimeObject();
            IMethod newDataMemberAttributeCtor = module.FindMethod(typeof(DataMemberAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);
            CustomAttributeDeclaration newDataMemberAttribute = new CustomAttributeDeclaration(newDataMemberAttributeCtor);

            int ordinal = 0;



           newDataMemberAttribute.NamedArguments.Add(
                new MemberValuePair(MemberKind.Property,
                    ordinal++,
                    "EmitDefaultValue",
                    new SerializedValue(boolType, false)));
            // TODO: take into account 'order'

           if (!String.IsNullOrEmpty(srcMemberAttr.Name))
           {
               newDataMemberAttribute.NamedArguments.Add(
                   new MemberValuePair(MemberKind.Property,
                       ordinal++,
                       "Name",
                       new SerializedValue(stringType,
                           originalValue ? "OriginalValue_" + srcMemberAttr.Name : srcMemberAttr.Name)
                           ));
           }

            if (originalValue)
            {
                newDataMemberAttribute.NamedArguments.Add(
                 new MemberValuePair(MemberKind.Property,
                     ordinal++,
                     "Order",
                     new SerializedValue(intType, 100+srcMemberAttr.Order)));
            }

            target.Add(newDataMemberAttribute);
        }


        #endregion

    }
}
